/*
 * ﻿Copyright (C) 2011-2013 NewMain Softech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.newmainsoftech.aspectjutil.eventmanager;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;

import org.aspectj.lang.Aspects;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.newmainsoftech.aspectjutil.adviseutil.Util;
import com.newmainsoftech.aspectjutil.eventmanager.EventInfo.EventerInfo;
import com.newmainsoftech.aspectjutil.eventmanager.label.EventListener;
import com.newmainsoftech.aspectjutil.eventmanager.label.EventTrigger;
import com.newmainsoftech.aspectjutil.eventmanager.label.OnEvent;
import com.newmainsoftech.aspectjutil.eventmanager.label.PreEvent;

/**
 * Event handler bridges between eventer and event listener. <br />
 * The eventer is either invocation of member method or value change on member field with 
 * &#64;{@link EventTrigger} annotation. <br />
 * The event listener is method (with &#64;{@link PreEvent} annotation and/or &#64;{@link OnEvent} 
 * annotation) of the event listener object annotated with &#64;{@link EventListener}.
 * <ul>
 * <li><h3>aspectjutil-eventmanager usage example</h3>
 * <ol>
 * <li>Create an event class for your event.<br />
 * For now, let's say it's <code>DemoEvent</code> class.
 * </li>
 * <li><b>Regarding eventer side</b>, add <code>&#64;EventTrigger</code> annotation to 
 * your eventer method or field.
 * <ul>
 * <li>If the execution of a method triggers DemoEvent event then:
 * <blockquote>
 * <code>
 * public class DemoEventer {<br />
 * &nbsp;&nbsp;&nbsp;&nbsp;<b>&#64;EventTrigger( DemoEvent.class)</b><br />
 * &nbsp;&nbsp;&nbsp;&nbsp;public void triggerDemoEvent() {<br />
 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...
 * </code>
 * </blockquote>
 * Putting <code>&#64;EventTrigger</code> annotation is supported regardless visibility and 
 * static/non-static of the eventer method.  
 * </li>
 * <li>If the value change of a member field triggers DemoEvent event then:
 * <blockquote>
 * <code>
 * public class DemoEventer {<br />
 * &nbsp;&nbsp;&nbsp;&nbsp;<b>&#64;EventTrigger( DemoEvent.class)</b><br />
 * &nbsp;&nbsp;&nbsp;&nbsp;private AnyType fieldTriggerDemoEvent;<br />
 * </code>
 * </blockquote>
 * Adding <code>&#64;EventTrigger</code> annotation is supported regardless visibility and 
 * static/non-static of the eventer field.  
 * </li>
 * </ul>
 * </li>
 * <li><b>Regarding event listener side</b>
 * <ol>
 * <li>Put <code>&#64;EventListener</code> annotation to the event listener class.
 * <blockquote>
 * <code>
 * <b>&#64;EventListener</b><br />
 * public class EventRecipient {<br />
 * &nbsp;&nbsp;&nbsp;&nbsp;...
 * </code>
 * </blockquote>
 * If the EventRecipient event listener instance is no longer necessary when any other object has  
 * no reference to it, then you can give {@link EventListener#gcSubject} attribute true, in order to 
 * avoid memory leakage.
 * </li>
 * <li>Put &#64;{@link PreEvent} and/or &#64;{@link OnEvent} annotation to event listener method.<br />
 * Your event listener method must have the single argument of {@link EventInfo} type.
 * <ul>
 * <li>To get the listener method <b>executed after the event occurrence</b>, then use 
 * <code>&#64;OnEvent</code> annotation.
 * <blockquote>
 * <code>
 * <b>&#64;OnEvent( value={DemoEvent.class})</b><br />
 * void onDemoEvent( <b>EventInfo</b> eventInfo) {<br />
 * &nbsp;&nbsp;&nbsp;&nbsp;...
 * </code>
 * </blockquote>
 * </li>
 * <li>To get the listener method <b>executed before the event occurrence</b>, then use 
 * <code>&#64;PreEvent</code> annotation. 
 * <blockquote>
 * <code>
 * <b>&#64;PreEvent( value={DemoEvent.class})</b><br />
 * void preDemoEvent( <b>EventInfo</b> eventInfo) {<br />
 * &nbsp;&nbsp;&nbsp;&nbsp;...
 * </code>
 * </blockquote>
 * </li>
 * <li>When your listener method needs <u>to subscribe to multiple different events</u>, then provides 
 * those event classes delimited with ','.
 * <blockquote>
 * <code>
 * &#64;OnEvent( <br />
 * &nbsp;&nbsp;&nbsp;&nbsp;value={<b>DemoEvent1.class,DemoEvent2.class</b>})<br />
 * void onDemoEvent( <b>EventInfo</b> eventInfo) {<br />
 * &nbsp;&nbsp;&nbsp;&nbsp;...
 * </code>
 * </blockquote>
 * </li>
 * <li>By default, the event listener method will be invoked only when the listening event is triggered 
 * by the eventer object instantiated within the same thread what is also instantiated the event listener 
 * object. <br />
 * To let the event listener method executed by the event triggered by the eventer object instantiated 
 * in other thread than one instantiated the event listener object, give false to 
 * {@link OnEvent#amongInstancesInThread} or/and {@link PreEvent#amongInstancesInThread} attribute.
 * </li>
 * <li>To set the execution order among the event lister methods, use {@link OnEvent#orderForEachEvent()} 
 * or/and {@link PreEvent#orderForEachEvent()} attribute.
 * <blockquote>
 * <code>
 * &#64;OnEvent( <br />
 * &nbsp;&nbsp;&nbsp;&nbsp;value={<b>DemoEvent1.class,DemoEvent2.class</b>}, <br />
 * &nbsp;&nbsp;&nbsp;&nbsp;<b>orderForEachEvent={0,3}</b>)<br />
 * void onDemoEvent( <b>EventInfo</b> eventInfo) {<br />
 * &nbsp;&nbsp;&nbsp;&nbsp;...<br />
 * </code>
 * </blockquote>
 * In that example case, among the event listener methods, the onDemoEvent method will have 
 * the turn zero (first execution order) for the DemoEvent1 event and the turn 3 (fourth execution 
 * order) for the DemoEvent2 event. <br />
 * For more information regarding other criterion of the execution order among the event listeners, 
 * refer to {@link SubscriberMethodUnitComparator#compare(SubscriberMethodUnit, SubscriberMethodUnit)}. 
 * </li>
 * </ul>
 * </li>
 * </ol>
 * </li>
 * </ol>
 * </li>
 * <li><h3>aspectjutil-eventmanager configuration</h3><br />
 * <ol>
 * <li>Make your project a AspectJ project.
 * </li>
 * <li>Add aspectjutil-eventmanager.jar to AspectJ's aspect path.
 * </li>
 * </ol> 
 * </li>
 * </ul>
 * 
 * @author <a href="mailto:artymt@gmail.com">Arata Y.</a>
 */
@Aspect
public class EventHandler {
	protected static Logger logger = LoggerFactory.getLogger( EventHandler.class);
	
	/* Sort order at onEventMethodMap and preEventMethodMap:
	 * 	1. event (done by key)
	 * 	2. turn in SubscriberMethodUnit
	 * 	3. staticMethod in SubscriberMethodUnit
	 */
	protected static Map<Class<?>, Set<SubscriberMethodUnit>> onEventMethodMap 
	= new ConcurrentHashMap<Class<?>, Set<SubscriberMethodUnit>>();
		/* Set<SubscriberMethodUnit> element for each event class should not contain 
		 * duplicate SubscriberMethodUnit.
		 */
	protected static Map<Class<?>, Set<SubscriberMethodUnit>> preEventMethodMap 
	= new ConcurrentHashMap<Class<?>, Set<SubscriberMethodUnit>>();
		/* Set<SubscriberMethodUnit> element for each event class should not contain 
		 * duplicate SubscriberMethodUnit.
		 */
	
	/**
	 * Remove provided <code>method</code> input from <code>subscriberMethodMap</code> input.
	 * @param subscriberMethodMap map of event and listening methods.
	 * @param method listener method being removed from <code>subscriberMethodMap</code> input
	 * @param methodClass Class of listener instance (does not mean to be method declaring class). 
	 * null is also allowed. 
	 * @param event listened by listener <code>method</code> input
	 */
	protected static boolean removeMethodFromSubscription(
			Map<Class<?>, Set<SubscriberMethodUnit>> subscriberMethodMap,
			Method method, Class<?> methodClass, Class<?> event) 
	{
		boolean methodRemoved = false;
		
		if ( ( method != null) && ( event != null)) {
			Set<SubscriberMethodUnit> subscriberMethodUnitSet = subscriberMethodMap.get( event);
				if ( subscriberMethodUnitSet == null) {
					return methodRemoved;
				}
			
			for( 
					Iterator<SubscriberMethodUnit> subscriberMethodUnitIterator 
					= subscriberMethodUnitSet.iterator();
					subscriberMethodUnitIterator.hasNext();
					) 
			{
				SubscriberMethodUnit subscriberMethodUnit = subscriberMethodUnitIterator.next();
				if ( method.equals( subscriberMethodUnit.getMethod())) {
					if ( methodClass != null) {
						if ( methodClass.equals( subscriberMethodUnit.getMethodClass())) {
							subscriberMethodUnitIterator.remove();
							methodRemoved = true;
								if ( logger.isDebugEnabled()) {
									logger.debug( 
											String.format(
													"Removed %1$s method of %2$s class from %3$s " 
													+ "subscription of %4$s event",
													method.toString(), 
													methodClass.toString(), 
													((subscriberMethodMap == EventHandler.preEventMethodMap) 
															? "PreEvent" : "OnEvent"),
													event.toString()
													)
											);
								}
							break;
						}
					}
					else {
						subscriberMethodUnitIterator.remove();
						methodRemoved = true;
							if ( logger.isDebugEnabled()) {
								logger.debug( 
										String.format(
												"Removed %1$s method of %2$s class from %3$s " 
												+ "subscription of %4$s event",
												method.toString(), 
												subscriberMethodUnit.getMethodClass().toString(), 
												((subscriberMethodMap == EventHandler.preEventMethodMap) 
														? "PreEvent" : "OnEvent"),
												event.toString()
												)
										);
							}
					}
				}
			} // for
		}
		return methodRemoved;
	}
	
	/**
	 * Remove method from pre-event subscription of event 
	 * @param method listener method being removed
	 * @param methodClass Class of listener instance (does not mean to be method declaring class).
	 * null is also allowed. 
	 * @param event listened by listener <code>method</code> input
	 * @return <code>true</code> when <code>method</code> input is removed, 
	 * and <code>false</code> when not.
	 */
	public static boolean removeMethodFromPreEventSubscription( 
			Method method, Class<?> methodClass, Class<?> event) 
	{
		return removeMethodFromSubscription( EventHandler.preEventMethodMap, method, methodClass, event);
	}
	/**
	 * Remove method from pre-event subscription of all known-events at moment of execution.
	 * @param method listener method being removed
	 * @param methodClass Class of listener instance (does not mean to be method declaring class).
	 * null is also allowed.
	 * @return {@link List} of event type what given listener <code>method</code> input is removed 
	 * from pre-event subscription map
	 */
	public static List<Class<?>> removeMethodFromPreEventSubscription( Method method, Class<?> methodClass) 
	{
		List<Class<?>> eventList = new ArrayList<Class<?>>();
				
		for( Class<?> event : EventHandler.preEventMethodMap.keySet()) {
			if ( removeMethodFromSubscription( EventHandler.preEventMethodMap, method, methodClass, event)) {
				eventList.add( event);
			}
		} // for
		
		return eventList;
	}
	
	/**
	 * Remove method from on-event subscription of event 
	 * @param method listener method being removed
	 * @param methodClass Class of listener instance (does not mean to be method declaring class).
	 * null is also allowed. 
	 * @param event listened by listener <code>method</code> input
	 * @return <code>true</code> when <code>method</code> input is removed, 
	 * and <code>false</code> when not.
	 */
	public static boolean removeMethodFromOnEventSubscription( 
			Method method, Class<?> methodClass, Class<?> event) 
	{
		return removeMethodFromSubscription( EventHandler.onEventMethodMap, method, methodClass, event);
	}
	/**
	 * Remove method from on-event subscription of all known-events at moment of execution.
	 * @param method listener method being removed
	 * @param methodClass Class of listener instance (does not mean to be method declaring class).
	 * null is also allowed.
	 * @return {@link List} of event type what given listener <code>method</code> input is removed 
	 * from pre-event subscription map
	 */
	public static List<Class<?>> removeMethodFromOnEventSubscription( Method method, Class<?> methodClass) 
	{
		List<Class<?>> eventList = new ArrayList<Class<?>>();
		
		for( Class<?> event : EventHandler.onEventMethodMap.keySet()) {
			if ( removeMethodFromSubscription( EventHandler.onEventMethodMap, method, methodClass, event)) {
				eventList.add( event);
			}
		} // for
		
		return eventList;
	}
	
	/**
	 * Remove method from subscription of all known-events at moment of execution.
	 * @param method listener method being removed from event subscription.
	 * @param methodClass Class of listener instance (does not mean to be method declaring class).
	 * null is also allowed.
	 */
	public static void removeMethodFromSubscription( Method method, Class<?> methodClass) {
		Set<Class<?>> preEventTypeSet = EventHandler.preEventMethodMap.keySet();
		Set<Class<?>> onEventTypeSet = EventHandler.onEventMethodMap.keySet();
		
		for( Class<?> event : preEventTypeSet) {
			removeMethodFromPreEventSubscription( method, methodClass, event);
		} // for
		for( Class<?> event : onEventTypeSet) {
			removeMethodFromOnEventSubscription( method, methodClass, event);
		} // for
	}
	
//TODO add method to change turn
//TODO add method to get turn
//TODO add contains method to check if method is contained in onEventMethodMap
//TODO add add method to add a method to onEventMethodMap
	
	/**
	 * Check whether <code>descendantMethod<code> input is overridden of <code>ancestorMethod</code> input
	 * @param descendantMethod suppose method of sub-class
	 * @param ancestorMethod suppose method of super class 
	 * @return true when <code>descendantMethod</code> is identical to or overridden 
	 * of <code>ancestorMethod</code>. Otherwise, <code>false</code>.
	 */
	public static boolean isOverriddenMethod( Method descendantMethod, Method ancestorMethod) {
		if ( descendantMethod.equals( ancestorMethod)) return true;
		
		Class<?> descendantClass = descendantMethod.getDeclaringClass();
		Class<?> ancestorClass = ancestorMethod.getDeclaringClass();
			if ( !ancestorClass.isAssignableFrom( descendantClass)) {
				return false;
			}
		
		int ancestorModifiers = ancestorMethod.getModifiers();
			if ( Modifier.isPrivate( ancestorModifiers)) {
				return false;
			}
			if ( Modifier.isFinal( ancestorModifiers)) {
				return false;
			}
			boolean isDefaultVisibility 
			= ((ancestorModifiers & EventHandler.VisibilityModifiersExceptPackageLevel) < 1);
			if ( isDefaultVisibility) {
				if ( !ancestorClass.getPackage()
						.equals( descendantClass.getPackage())) return false;
			}
		
		if ( 
				!ancestorMethod.getName()
				.equals( descendantMethod.getName())
				) return false;
		if ( 
				!Arrays.equals( 
						ancestorMethod.getParameterTypes(), descendantMethod.getParameterTypes())
				) return false;
		if ( 
				!ancestorMethod.getGenericReturnType()
				.equals( descendantMethod.getGenericReturnType())
				) return false;
		
		return true;
	} // public static boolean isOverriddenMethod( Method descendantMethod, Method ancestorMethod)
	
	public static final int VisibilityModifiersExceptPackageLevel 
	= (Modifier.PUBLIC + Modifier.PROTECTED + Modifier.PRIVATE); 

	/**
	 * Check whether <code>descendantMethodSet<code> contains overridden one of <code>method</code> input. 
	 * Unless visibility modifier of <code>method</code> input is either default (package level) or private, 
	 * check elements of <code>descendantMethodSet<code> whether it's identified as same with 
	 * <code>method</code> input regarding the following properties: 
	 * <ul>
	 * <li>method name</li>
	 * <li>types of parameters</li>
	 * <li>return type</li>
	 * </ul>
	 * If visibility modifier of method input is private or final, then return false. <br /> 
	 * If visibility modifier of method input is default (package level), then, before checking 
	 * the above conditions, check whether class of method input is in the same package of subjectClass.
	 * 
	 * @param method suppose to be a method in super class of <code>subjectClass</code> input
	 * @param subjectClass should be class declaring <code>method</code> input or its sub-class.
	 * @param descendantMethodSet set of methods to search overridden one for <code>method</code> input; 
	 * so should be the set of methods from sub-class of <code>subjectClass</code> input.
	 * @return true when <code>descendantMethodSet</code> input contains overridden one of 
	 * <code>method</code> input.
	 * @throws IllegalArgumentException when <code>subjectClass</code> input is not from lineage of 
	 * class defining <code>method</code> input.
	 */
	protected static boolean isOverriddenMethod( 
			Method method, Class<?> subjectClass, Set<Method> descendantMethodSet) {
		
		Class<?> declaringClass = method.getDeclaringClass();
			if ( !declaringClass.isAssignableFrom( subjectClass)) {
				throw new IllegalArgumentException( 
						String.format(
								"%1$s method is not defined in the lineage of %2$s.",
								method.toString(),
								subjectClass.toString()
								)
						);
			}
		
		int methodModifiers = method.getModifiers();
			if ( Modifier.isPrivate( methodModifiers)) {
				return false;
			}
			if ( Modifier.isFinal( methodModifiers)) {
				return false;
			}
			boolean isDefaultVisibility 
			= ((methodModifiers & EventHandler.VisibilityModifiersExceptPackageLevel) < 1);
			if ( isDefaultVisibility) {
				if ( !method.getDeclaringClass().getPackage()
						.equals( subjectClass.getPackage())) return false;
			}
		
		// Check whehter method has been overridden in descendant class
		String methodName = method.getName();
		Class<?>[] parameterTypes = method.getParameterTypes();
		Type methodReturnType = method.getGenericReturnType();
		
		boolean overrideFlag = false;
		synchronized( descendantMethodSet) {
			for( Method descendantMethod : descendantMethodSet) {
				if ( !methodName.equals( descendantMethod.getName())) continue;
				if ( !Arrays.equals( parameterTypes, descendantMethod.getParameterTypes())) continue;
				if ( !methodReturnType.equals( descendantMethod.getGenericReturnType())) continue;
				
				overrideFlag = true;
				break;
			} // for
			descendantMethodSet.notifyAll();
		} // synchronized
		
		return overrideFlag;
	} // public static boolean isOverriddenMethod( Method method, Set<Method> descendantMethodSet)
	
	protected static SubscriberMethodUnitComparator subscriberMethodUnitComparator
	= new SubscriberMethodUnitComparator();
	
	protected void addMethodToEventSubscriberMethodMap( 
			Map<Class<?>, Set<SubscriberMethodUnit>> subscriberMethodMap,
			Method method, Class<?> methodOwner, 
			Class<?>[] eventClassArray, boolean amongInstancesInThread, int[] orderForEachEventArray) 
	{
		for( int eventIndex = 0; eventIndex < eventClassArray.length; eventIndex++) {
			SubscriberMethodUnit subscriberMethodUnit;
			if ( eventIndex < orderForEachEventArray.length) {
				subscriberMethodUnit
				= new SubscriberMethodUnit( 
						method, 
						methodOwner,
						amongInstancesInThread, 
						orderForEachEventArray[ eventIndex]
						);
			}
			else {
				subscriberMethodUnit
				= new SubscriberMethodUnit( 
						method, 
						methodOwner, 
						amongInstancesInThread, 
						SubscriberMethodUnit.DEFAULT_TURN_VALUE
						);
			}
			
			Set<SubscriberMethodUnit> subscriberMethodUnitSet 
			= subscriberMethodMap.get( eventClassArray[ eventIndex]);
				if ( subscriberMethodUnitSet == null) {
					subscriberMethodUnitSet 
					= new ConcurrentSkipListSet<SubscriberMethodUnit>( 
							EventHandler.subscriberMethodUnitComparator);
					subscriberMethodMap.put( eventClassArray[ eventIndex], subscriberMethodUnitSet);
				}
			
			boolean subscriberMethodUnitAdded = subscriberMethodUnitSet.add( subscriberMethodUnit);
			if ( !subscriberMethodUnitAdded) {
				if ( logger.isWarnEnabled()) {
					if ( !subscriberMethodUnitSet.contains( subscriberMethodUnit)) {
						logger.warn(
								String.format(
										"Failed to add %1$s method to list of event listener methods " 
										+ "annotated with %2$s.%nPlease report this incidence.",
										method.toGenericString().replace( 
												method.getDeclaringClass().getName(), 
												methodOwner.getName()
												), 
										((subscriberMethodMap == EventHandler.onEventMethodMap) 
												? "@OnEvent" : "@PreEvent")
										)
								);
					}
					else {
						if ( !Modifier.isStatic( method.getModifiers())) {
							logger.warn(
									String.format(
											"Detected the attempt of adding duplication of %1$s method to list " 
											+ "of event listener methods annotated with %2$s.%nThis may be " 
											+ "caused by multiple class loaders if it has not been intentionally " 
											+ "added before.",
											method.toGenericString().replace( 
													method.getDeclaringClass().getName(), 
													methodOwner.getName()
													), 
											((subscriberMethodMap == EventHandler.onEventMethodMap) 
													? "@OnEvent" : "@PreEvent")
											)
									);
						}
					}
				}
			}
			
//			subscriberMethodMap.put( eventClassArray[ eventIndex], subscriberMethodUnitSet);
		} // for
	} // protected void addMethodToEventSubscriberMethodMap(.....)
	
	protected void addMethodToOnEventMethodMap( Method method, Class<?> methodOwner, OnEvent onEvent) {
		Class<?>[] eventClassArray = onEvent.value();
		boolean amongInstancesInThread = onEvent.amongInstancesInThread();
		int[] orderForEachEventArray = onEvent.orderForEachEvent();
		
		addMethodToEventSubscriberMethodMap( 
				EventHandler.onEventMethodMap, 
				method, methodOwner, eventClassArray, amongInstancesInThread, orderForEachEventArray);
		
		if ( logger.isDebugEnabled()) {
			if ( Modifier.isStatic( method.getModifiers())) {
				logger.debug(
						String.format( 
								"added %1$s static method to list of methods annotated with " 
								+ "@OnEvent for %2$s event(s)", 
								method.toGenericString(),
								Arrays.toString( onEvent.value())
								)
						);
			}
			else {
				logger.debug(
						String.format( 
								"added %1$s method to list of methods annotated with " 
								+ "@OnEvent for %2$s event(s)", 
								method.toGenericString().replace( 
										method.getDeclaringClass().getName(), 
										methodOwner.getName()
										),
								Arrays.toString( onEvent.value())
								)
						);
			}
		}
	}
	
	protected void addMethodToPreEventMethodMap( Method method, Class<?> methodOwner, PreEvent preEvent) {
		Class<?>[] eventClassArray = preEvent.value();
		boolean amongInstancesInThread = preEvent.amongInstancesInThread();
		int[] orderForEachEventArray = preEvent.orderForEachEvent();

		addMethodToEventSubscriberMethodMap( 
				EventHandler.preEventMethodMap, 
				method, methodOwner, eventClassArray, amongInstancesInThread, orderForEachEventArray);
		
		if ( logger.isDebugEnabled()) {
			if ( Modifier.isStatic( method.getModifiers())) {
				logger.debug(
						String.format( 
								"added %1$s static method to list of methods annotated with " 
								+ "@PreEvent for %2$s event(s)", 
								method.toGenericString(),
								Arrays.toString( preEvent.value())
								)
						);
			}
			else {
				logger.debug(
						String.format( 
								"added %1$s method to list of methods annotated with " 
								+ "@PreEvent for %2$s event(s)", 
								method.toGenericString().replace( 
										method.getDeclaringClass().getName(), 
										methodOwner.getName()
										),
								Arrays.toString( preEvent.value())
								)
						);
			}
		}
	}
	
	protected void addNonPrivateMethodToEventMethodMap( 
			Method method, int methodModifiers, Class<?>[] parameterTypes, Class<?> subjectClass,  
			Set<Method> nonOverridenDescendantMethodSet, 
			OnEvent onEvent, PreEvent preEvent 
			) 
	{
		if ( Modifier.isFinal( methodModifiers)) { 
			nonOverridenDescendantMethodSet.add( method);
			
			// Check whether method is annotated with @OnEvent 
			if ( onEvent != null) {
				/* Add non-static method excluding inherited ones to onEventMethodMap.
				 * Providing subjectClass since instance object is matter for non-static method case
				 */
				addMethodToOnEventMethodMap( method, subjectClass, onEvent);
			}
			// Check whether method is annotated with @PreEvent 
			if ( preEvent != null) {
				/* Add non-static method excluding inherited ones to preEventMethodMap.
				 * Providing subjectClass since instance object is matter for non-static method case
				 */
				addMethodToPreEventMethodMap( method, subjectClass, preEvent);
			}
		}
		else {
			// Check whehter method has been overridden in descendant class
			String methodName = method.getName();
			Type methodReturnType = method.getGenericReturnType();
			
			boolean overrideFlag = false;
			for( Method descendantMethod : nonOverridenDescendantMethodSet) {
				if ( !methodName.equals( descendantMethod.getName())) continue;
				if ( !Arrays.equals( parameterTypes, descendantMethod.getParameterTypes())) continue;
				if ( !methodReturnType.equals( descendantMethod.getGenericReturnType())) continue;
				
				overrideFlag = true;
				break; // for
			} // for
			
			if ( !overrideFlag) {
				nonOverridenDescendantMethodSet.add( method);
				
				if ( !Modifier.isAbstract( methodModifiers)) {
					// Check whether method is annotated with @OnEvent 
					if ( onEvent != null) {
						/* Add non-static method excluding inherited ones to onEventMethodMap.
						 * Providing subjectClass since instance object is matter for non-static method case
						 */
						addMethodToOnEventMethodMap( method, subjectClass, onEvent);
					}
					// Check whether method is annotated with @PreEvent 
					if ( preEvent != null) {
						/* Add non-static method excluding inherited ones to preEventMethodMap.
						 * Providing subjectClass since instance object is matter for non-static method case
						 */
						addMethodToPreEventMethodMap( method, subjectClass, preEvent);
					}
				}
			}
		}
	}
	
	/**
	 * Scan through class hierarchy by recursive execution for methods annotated with {@link OnEvent} 
	 * and add found event-listener methods to subscription with information of listening events. 
	 * 
	 * @param subjectClass class being hunted for event listener method annotated with either 
	 * {@link PreEvent} or {@link OnEvent}
	 * @param classObj suppose to be super class of <code>subjectClass</code>
	 * @param nonOverridenDescendantMethodSet {@link Set} what, at end, will be filled only with 
	 * {@link Method} object not overridden by any sub-classes.
	 */
	protected void huntListenerMethods( 
			Class<?> subjectClass, Class<?> classObj, Set<Method> nonOverridenDescendantMethodSet) 
	{
		for( Method method : classObj.getDeclaredMethods()) {
			OnEvent onEvent = method.getAnnotation( OnEvent.class);
			PreEvent preEvent = method.getAnnotation( PreEvent.class); 
			
			// validate @OnEvent method and @PreEvent method 
			Class<?>[] parameterTypes = method.getParameterTypes();
			if ( ( onEvent != null) || ( preEvent != null)) { 
				// Check on argument of method; should have only EventInfo type argument. 
				if ( parameterTypes.length != 1 || !EventInfo.class.isAssignableFrom( parameterTypes[ 0])) {
					throw new RuntimeException( 
							String.format(
									"Bad method signature at %1$s%n" 
									+ "Method annotated with either @OnEvent or @PreEvent is allowed to " 
									+ "have only EventInfo parameter.", 
									method.toString() 
									)
							);
				}
			}
			
			
			int methodModifiers = method.getModifiers();
			if ( Modifier.isStatic( methodModifiers)) {
				if ( Modifier.isAbstract( methodModifiers)) {
					continue; // for
				}

				// Check whether method is annotated with @OnEvent 
				if ( onEvent != null) {
					/* All static method should be added onEventMethodMap.
					 * Providing classObj since class defining method is matter for static method case 
					 */
					addMethodToOnEventMethodMap( method, classObj, onEvent);
				}
				if ( preEvent != null) {
					/* All static method should be added preEventMethodMap.
					 * Providing classObj since class defining method is matter for static method case 
					 */
					addMethodToPreEventMethodMap( method, classObj, preEvent);
				}
				
				continue; // for
			}
			else { // non-static method
				if ( subjectClass.equals( classObj)) {
					if ( Modifier.isAbstract( subjectClass.getModifiers())) {
						continue; // for
					}
				}
				
				// check on visibility modifier of method
				if ( Modifier.isPrivate( methodModifiers)) {
					// Do not add private visibility method to nonOverridenDescendantMethodSet
					
					if ( Modifier.isAbstract( methodModifiers)) {
						continue; // for
					}
					
					if ( subjectClass.equals( classObj)) {
						// Check whether method is annotated with @OnEvent 
						if ( onEvent != null) {
							/* Add non-static method excluding inherited ones to onEventMethodMap.
							 * Providing subjectClass since instance object is matter for non-static method case
							 */
							addMethodToOnEventMethodMap( method, subjectClass, onEvent);
						}
						// Check whether method is annotated with @PreEvent 
						if ( preEvent != null) {
							/* Add non-static method excluding inherited ones to preEventMethodMap.
							 * Providing subjectClass since instance object is matter for non-static method case
							 */
							addMethodToPreEventMethodMap( method, subjectClass, preEvent);
						}
					}
					
					continue; // for
				}
				else {
					boolean isDefaultVisibility 
					= ((methodModifiers & EventHandler.VisibilityModifiersExceptPackageLevel) < 1);
					if ( isDefaultVisibility) {
						if ( 
								subjectClass.equals( classObj) 
								|| (classObj.isAssignableFrom( subjectClass) 
										&& classObj.getPackage().equals( subjectClass.getPackage()))
								) 
						{
							addNonPrivateMethodToEventMethodMap( 
									method, methodModifiers, parameterTypes, subjectClass, 
									nonOverridenDescendantMethodSet, 
									onEvent, preEvent);
							
							continue; // for
						}
					}
					else { // either public or protected modifier
						addNonPrivateMethodToEventMethodMap( 
								method, methodModifiers, parameterTypes, subjectClass, 
								nonOverridenDescendantMethodSet, 
								onEvent, preEvent);
						
						continue; // for
					}
				}
			}
		} // for
		
		Class<?> superClass = classObj.getSuperclass();
		if ( superClass != Object.class) {
			huntListenerMethods( subjectClass, superClass, nonOverridenDescendantMethodSet);
		}
	} // protected void huntListenerMethods( Class<?> subjectClass, Class<?> classObj, Set<Method> nonOverridenDescendantMethodSet)
	
	/**
	 * Dummy method for {@link Pointcut} what cross-cut loading of event listener class. 
	 * This <code>Pointcut</code> is advised by {@link #beforeEventListenerStaticinitialization(JoinPoint)} 
	 * and {@link #afterEventListenerStaticinitialization(JoinPoint)}. 
	 */
	@Pointcut( "staticinitialization( @com.newmainsoftech.aspectjutil.eventmanager.label.EventListener *)")
	public static void eventListenerStaticinitializationPointcut() {}
	/**
	 * {@link Before} advise method to enforce that {@link EventListener} annotation is only 
	 * applied to class, not interface. 
	 * There shouldn't be necessity of calling this method intentionally. 
	 * @param joinPoint
	 * @see #eventListenerStaticinitializationPointcut()
	 */
	@Before( value="eventListenerStaticinitializationPointcut()")
	public void beforeEventListenerStaticinitialization( JoinPoint joinPoint) {
		if ( logger.isDebugEnabled()) {
			logger.debug(
					String.format(
							"%1$s has been picked up (by AspectJ's staticinitialization) " 
							+ "as annotated with @EventListener",
							joinPoint.getSignature().toString()
							)
					);
		}

		Class<?> eventListenerClass = joinPoint.getSignature().getDeclaringType();
		if ( eventListenerClass.isInterface()) {
			throw new RuntimeException(
						String.format(
								"@EventListener annotation should be applied only to class but " 
								+ "%1$s is interface.",
								eventListenerClass.toString()
								)
					);
		}
	}
	
	/**
	 * {@link AfterReturning} advice method to hunt down event listener methods annotated either 
	 * {@link PreEvent} or {@link OnEvent} at loading of event listener class annotated 
	 * with {@link EventListener}.
	 * There shouldn't be necessity of calling this method intentionally. 
	 * @param joinPoint
	 * @see #eventListenerStaticinitializationPointcut()
	 */
	@AfterReturning( pointcut="eventListenerStaticinitializationPointcut()")
	public void afterEventListenerStaticinitialization( JoinPoint joinPoint) {
		Class<?> eventListenerClass = joinPoint.getSignature().getDeclaringType();
		Set<Method> nonOverridenDescendantMethodSet = new LinkedHashSet<Method>();
		huntListenerMethods( 
				eventListenerClass, eventListenerClass, nonOverridenDescendantMethodSet);
	}
	
	
	protected Method getMethod( Class<?> classObj, String methodName, Class<?>[] parameterTypes) {
		Method method = null;
		try {
			method = classObj.getDeclaredMethod( methodName, parameterTypes);
		}
		catch( NoSuchMethodException exception) {
			/* Do nothing */
		}
		catch( Throwable throwable) {
/*			if ( logger.isWarnEnabled()) {
				logger.warn( 
						String.format(
								"Failed in attempting to obtain %1$s method in %2 class by reflection.", 
								methodName, classObj.getName()
								), 
						throwable
						);
			}
			return null;
*/
			throw new RuntimeException( 
					String.format(
							"Failed in attempting to obtain %1$s method in %2 class by reflection.", 
							methodName, classObj.getName()
							), 
					throwable
					);
		}
		
		if ( method == null) {
			Class<?> superClass = classObj.getSuperclass();
			if ( superClass == null) return null;
			
			method = getMethod( superClass, methodName, parameterTypes);
		}
			
		return method;
	} // protected Method getMethod( Class<?> classObj, String methodName, Class<?>[] parameterTypes)
	
	protected void publishEvent( 
			Map<Class<?>, Set<SubscriberMethodUnit>> subscriberMethodMap, EventInfo eventInfo) {
		Class<?> event = eventInfo.getTriggeredEvent();
		
		Set<SubscriberMethodUnit> subscriberMethodUnitSet = subscriberMethodMap.get( event);
			if ( subscriberMethodUnitSet == null) {
				if ( logger.isDebugEnabled()) {
					logger.debug(
							String.format(
									"No %1$s type listener for %2$s event",
									((subscriberMethodMap == EventHandler.onEventMethodMap) 
											? "@OnEvent" : "@PreEvent"),
									event.getName()
									)
							);
				}
				
				return;
			}
		
		StringBuffer stringBuffer = new StringBuffer();
		for( SubscriberMethodUnit subscriberMethodUnit : subscriberMethodUnitSet) {
			Method listeningMethod = subscriberMethodUnit.getMethod();
			listeningMethod.setAccessible( true);
			Class<?> methodClass = subscriberMethodUnit.getMethodClass();
			
			if ( Modifier.isStatic( listeningMethod.getModifiers())) {
				if ( subscriberMethodUnit.isAmongInstancesInThread()) {
					String methodName = methodClass.getName() + "." + listeningMethod.getName();
					if ( logger.isInfoEnabled()) {
						logger.info( 
								String.format(
										"amongInstancesInThread attribute setting of %1$s annotation is " 
										+ "ignored as %2$s is static method.",
										((subscriberMethodMap == EventHandler.onEventMethodMap) 
												? "@OnEvent" : "@PreEvent"),
										methodName
										)
								);
					}
				}
				
				try {
					listeningMethod.invoke( null, eventInfo);
					
					if ( logger.isDebugEnabled()) {
						String methodName = methodClass.getName() + "." + listeningMethod.getName();
						logger.debug(
								String.format( 
										"Invoked %1$s event-listener static method (annotated " 
										+ "with %2$s) %3$s %4$s event", 
										methodName, 
										((subscriberMethodMap == EventHandler.onEventMethodMap) 
												? "@OnEvent" : "@PreEvent"),
										((subscriberMethodMap == EventHandler.onEventMethodMap) 
												? "at" : "before"),
										event.getName()
										)
								);
					}
				}
				catch( Throwable throwable) {
					subscriberMethodUnitSet.remove( subscriberMethodUnit);
					
					String methodName = methodClass.getName() + "." + listeningMethod.getName();
					stringBuffer.append( "\n" + methodName);
					if ( logger.isErrorEnabled()) {
						logger.error(
								String.format(
										"%1$s event listener static method (annotated with %2$s) won't be " 
										+ "executed any longer %3$s further %4$s event due to this experience " 
										+ "of failure in its execution.",
										methodName,
										((subscriberMethodMap == EventHandler.onEventMethodMap) 
												? "@OnEvent" : "@PreEvent"),
										((subscriberMethodMap == EventHandler.onEventMethodMap) 
												? "at" : "before"),
										event.getName()
										),
								throwable 
								);
					}
				}
			}
			else {
				long threadId = Thread.currentThread().getId();
				
				Map<Object, Long> eventListerInstanceMap 
				= Aspects.aspectOf( EventListenerInstanceTracker.class, methodClass)
					.getListeners( methodClass);
				for( Entry<Object, Long> eventListerInstanceEntry : eventListerInstanceMap.entrySet()) {
					// eventListerInstanceEntry consists of event listener instance (object) and its thread id (long)
					
					if ( subscriberMethodUnit.isAmongInstancesInThread()) {
						if ( threadId != eventListerInstanceEntry.getValue().longValue()) continue; // for
					}
					
					try {
						listeningMethod.invoke( eventListerInstanceEntry.getKey(), eventInfo);
						
						if ( logger.isDebugEnabled()) {
							String methodName = methodClass.getName() + "." + listeningMethod.getName();
							logger.debug(
									String.format(
											"Invoked %1$s event-listener method (annotated " 
											+ "with %2$s) of %3$s instance %4$s %5$s event", 
											methodName, 
											((subscriberMethodMap == EventHandler.onEventMethodMap) 
													? "@OnEvent" : "@PreEvent"),
											eventListerInstanceEntry.getKey().toString(),
											((subscriberMethodMap == EventHandler.onEventMethodMap) 
													? "at" : "before"),
											event.getName()
											)
									);
						}
					}
					catch( Throwable throwable) {
						subscriberMethodUnitSet.remove( subscriberMethodUnit);
						
						String methodName = methodClass.getName() + "." + listeningMethod.getName();
						stringBuffer.append( "\n" + methodName);
						if ( logger.isErrorEnabled()) {
							logger.error(
									String.format(
											"%1$s event listener method (annotated with %2$s) won't be " 
											+ "executed any longer %3$s further %4$s event due to this " 
											+ "experience of failure in its execution.",
											methodName,
											((subscriberMethodMap == EventHandler.onEventMethodMap) 
													? "@OnEvent" : "@PreEvent"),
											((subscriberMethodMap == EventHandler.onEventMethodMap) 
													? "at" : "before"),
											event.getName()
											),
									throwable 
									);
						}
					}
				} // for
			}
		} // for
		if ( stringBuffer.length() > 0) {
			throw new RuntimeException( 
					String.format(
							"Any further %1$s event won't be published to the event listener " 
							+ "method(s) below due to previous occurance of failure(s). Please check error " 
							+ "logs for each failure. %2$s",
							event.getName(), 
							stringBuffer.toString()
							) 
					);
		}
	} // protected void publishEvent( EventInfo eventInfo)
	
	
	/**
	 * Dummy method for {@link Pointcut} what cross-cut execution of eventer method annotated with 
	 * {@link EventTrigger}. 
	 * This <code>Pointcut</code> is advised by {@link #beforeExecutingEventTrigger(JoinPoint, EventTrigger)}  
	 * and {@link #afterExecutingEventTrigger(JoinPoint, EventTrigger, Object)}. 
	 * 
	 * @param eventTrigger
	 */
	@Pointcut( 
			"execution( @com.newmainsoftech.aspectjutil.eventmanager.label.EventTrigger * *(..)) " +
			"&& @annotation( eventTrigger)"
			)
	public static void executingEventTriggerPointcut( EventTrigger eventTrigger) {}
	
	/**
	 * {@link Before} advise method to invoke listener method annotated with {@link PreEvent} 
	 * for triggered event. 
	 * There shouldn't be necessity of calling this method intentionally. 
	 * 
	 * @param joinPoint
	 * @param eventTrigger 
	 * @see #executingEventTriggerPointcut(EventTrigger)
	 */
	@Before( value="executingEventTriggerPointcut( eventTrigger)", argNames="eventTrigger")
	public void beforeExecutingEventTrigger( JoinPoint joinPoint, EventTrigger eventTrigger) {
		Class<?> triggeredEvent = eventTrigger.value();
		if ( logger.isDebugEnabled()) {
			logger.debug(
					String.format(
							"Going to execute @PreEvent type event listener method(s) for %1$s event " +
							"prior to execution of method below: %n%2$s",
							triggeredEvent.getName(), 
							Util.getJoinPointInfo( joinPoint, null)
							)
					);
		}
		
		Object targetObj = joinPoint.getTarget();
		Class<?> declaringType = ((CodeSignature)joinPoint.getSignature()).getDeclaringType();
		String methodName = ((CodeSignature)joinPoint.getSignature()).getName();
		Class<?>[] parameterTypes = ((CodeSignature)joinPoint.getSignature()).getParameterTypes();
		
		Method method = getMethod( declaringType, methodName, parameterTypes);
		
		EventerInfo eventerInfo = new EventerInfo( targetObj, method);
		EventInfo eventInfo = new EventInfo( triggeredEvent, eventerInfo, joinPoint.getArgs(), null);
		
		publishEvent( EventHandler.preEventMethodMap, eventInfo);
	}
	
	/**
	 * {@link AfterReturning} advise method to invoke listener method annotated with {@link OnEvent} 
	 * for triggered event. 
	 * There shouldn't be necessity of calling this method intentionally. 
	 * 
	 * @param joinPoint
	 * @param eventTrigger
	 * @param returnedObj
	 * @see #executingEventTriggerPointcut(EventTrigger)
	 */
	@AfterReturning( 
			pointcut="executingEventTriggerPointcut( eventTrigger)", 
			returning="returnedObj",
			argNames="eventTrigger,returnedObj"
			)
	public void afterExecutingEventTrigger( 
			JoinPoint joinPoint, EventTrigger eventTrigger, Object returnedObj) {
		Class<?> triggeredEvent = eventTrigger.value();
		if ( logger.isDebugEnabled()) {
			logger.debug(
					String.format(
							"Going to execute @OnEvent type event listener method(s) for %1$s event " +
							"triggered from execution of method below: %n%2$s",
							triggeredEvent.getName(), 
							Util.getJoinPointInfo( joinPoint, null)
							)
					);
		}
		
		Object targetObj = joinPoint.getTarget();
		Class<?> declaringType = ((CodeSignature)joinPoint.getSignature()).getDeclaringType();
		String methodName = ((CodeSignature)joinPoint.getSignature()).getName();
		Class<?>[] parameterTypes = ((CodeSignature)joinPoint.getSignature()).getParameterTypes();
		
		Method method = getMethod( declaringType, methodName, parameterTypes);
		
		EventerInfo eventerInfo = new EventerInfo( targetObj, method);
		EventInfo eventInfo = new EventInfo( triggeredEvent, eventerInfo, joinPoint.getArgs(), returnedObj);
		
		publishEvent( EventHandler.onEventMethodMap, eventInfo);
	}
	
	
	protected Field getField( Class<?> classObj, String fieldName) {
		Field field = null;
		try {
			field = classObj.getDeclaredField( fieldName);
		}
		catch( NoSuchFieldException exception) {
			/* Do nothing */
		}
		catch( Throwable throwable) {
/*			if ( logger.isWarnEnabled()) {
				logger.warn( 
						String.format(
								"Failed in attempting to obtain %1$s field in %2 class by reflection.", 
								fieldName, classObj.getName()
								), 
						throwable
						);
			}
			return null;
*/
			throw new RuntimeException( 
					String.format(
							"Failed in attempting to obtain %1$s field in %2 class by reflection.", 
							fieldName, classObj.getName()
							), 
					throwable
					);
		}
		
		if ( field == null) {
			Class<?> superClass = classObj.getSuperclass();
			if ( superClass == null) return null;
			
			field = getField( superClass, fieldName);
		}
			
		return field;
	} // protected Method getField( Class<?> classObj, String fieldName)
	
	/**
	 * Dummy method for {@link Pointcut} what cross-cut value change of member field annotated with 
	 * {@link EventTrigger}. 
	 * This <code>Pointcut</code> is advised by {@link #beforeEventTriggerSetter(JoinPoint, EventTrigger, Object)}  
	 * and {@link #afterEventTriggerSetter(JoinPoint, EventTrigger, Object)}. 
	 * 
	 * @param eventTrigger
	 * @param fieldValue
	 */
	@Pointcut( 
			"set( @com.newmainsoftech.aspectjutil.eventmanager.label.EventTrigger * *) " 
			+ "&& @annotation( eventTrigger) && args( fieldValue)"
			)
	public static void eventTriggerSetterPointcut( EventTrigger eventTrigger, Object fieldValue) {}
	
	/**
	 * {@link Before} advise method to invoke listener method annotated with {@link PreEvent} 
	 * for triggered event. 
	 * There shouldn't be necessity of calling this method intentionally. 
	 * 
	 * @param joinPoint
	 * @param eventTrigger
	 * @param fieldValue
	 * @see #eventTriggerSetterPointcut(EventTrigger, Object)
	 */
	@Before( value="eventTriggerSetterPointcut( eventTrigger, fieldValue)")
	public void beforeEventTriggerSetter( JoinPoint joinPoint, EventTrigger eventTrigger, Object fieldValue) {
		Class<?> triggeredEvent = eventTrigger.value();
		if ( logger.isDebugEnabled()) {
			logger.debug(
					String.format(
							"Going to execute @PreEvent type event listener method(s) for %1$s event " +
							"prior to setting value to the field below: %n%2$s",
							triggeredEvent.getName(), 
							Util.getJoinPointInfo( joinPoint, null)
							)
					);
		}
		
		Object targetObj = joinPoint.getTarget();
		Class<?> declaringType = joinPoint.getSignature().getDeclaringType();
		String fieldName = joinPoint.getSignature().getName();
		
		Field field = getField( declaringType, fieldName);
		
		EventerInfo eventerInfo = new EventerInfo( targetObj, field);
		EventInfo eventInfo = new EventInfo( triggeredEvent, eventerInfo, null, fieldValue);
		
		publishEvent( EventHandler.preEventMethodMap, eventInfo);
	}
	
	/**
	 * {@link AfterReturning} advise method to invoke listener method annotated with {@link OnEvent} 
	 * for triggered event. 
	 * There shouldn't be necessity of calling this method intentionally. 
	 * 
	 * @param joinPoint
	 * @param eventTrigger
	 * @param fieldValue
	 * @see #eventTriggerSetterPointcut(EventTrigger, Object)
	 */
	@AfterReturning( 
			pointcut="eventTriggerSetterPointcut( eventTrigger, fieldValue)", 
			argNames="eventTrigger, fieldValue"
			)
	public void afterEventTriggerSetter( JoinPoint joinPoint, EventTrigger eventTrigger, Object fieldValue) {
		Class<?> triggeredEvent = eventTrigger.value();
		if ( logger.isDebugEnabled()) {
			logger.debug(
					String.format(
							"Going to execute @OnEvent type event listener method(s) for %1$s event " +
							"triggered from setting value to the field below: %n%2$s",
							triggeredEvent.getName(), 
							Util.getJoinPointInfo( joinPoint, null)
							)
					);
		}
		
		Object targetObj = joinPoint.getTarget();
		Class<?> declaringType = joinPoint.getSignature().getDeclaringType();
		String fieldName = joinPoint.getSignature().getName();
		
		Field field = getField( declaringType, fieldName);
		
		EventerInfo eventerInfo = new EventerInfo( targetObj, field);
		EventInfo eventInfo = new EventInfo( triggeredEvent, eventerInfo, null, fieldValue);
		
		publishEvent( EventHandler.onEventMethodMap, eventInfo);
	} // public void afterEventTriggerSetter( JoinPoint joinPoint, EventTrigger eventTrigger, Object fieldValue)
	
//TODO Create the method like EventHandler.huntListenerMethods method for @EventTriger in order to support inheritance of @EventTriger. 
}
